package com.atguigu.gulimall.ware.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.constant.order.OrderConstant;
import com.atguigu.common.constant.ware.WareOrderTaskConstant;
import com.atguigu.common.exception.NoStockException;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.ware.entity.WareOrderTaskDetailEntity;
import com.atguigu.gulimall.ware.entity.WareOrderTaskEntity;
import com.atguigu.gulimall.ware.feign.OrderFeignService;
import com.atguigu.gulimall.ware.feign.ProductFeignService;
import com.atguigu.gulimall.ware.mq.StockDetailTo;
import com.atguigu.gulimall.ware.mq.StockLockedTo;
import com.atguigu.gulimall.ware.to.OrderTo;
import com.atguigu.gulimall.ware.to.WareSkuLockTo;
import com.atguigu.gulimall.ware.vo.OrderItemVo;
import com.atguigu.gulimall.ware.vo.SkuHasStockVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;

import com.atguigu.gulimall.ware.dao.WareSkuDao;
import com.atguigu.gulimall.ware.entity.WareSkuEntity;
import com.atguigu.gulimall.ware.service.WareSkuService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;


//@RabbitListener(queues = "stock.release.stock.queue")
@Slf4j
@Service("wareSkuService")
public class WareSkuServiceImpl extends ServiceImpl<WareSkuDao, WareSkuEntity> implements WareSkuService {

    @Resource
    private WareSkuDao wareSkuDao;
    @Resource
    private ProductFeignService productFeignService;
    @Resource
    private WareOrderTaskServiceImpl orderTaskService;
    @Resource
    private WareOrderTaskDetailServiceImpl orderTaskDetailService;
    @Resource
    private RabbitTemplate rabbitTemplate;
    @Resource
    private OrderFeignService orderFeignService;
    /*
    * 模拟业务：
    * 1.下订单成功，库存锁定成功，接下来的业务调用失败，导致订单回滚。之前锁定的库存就要自动解锁。
    * 2.订单失败。锁库存失败
    */
/*    @RabbitHandler()
    public void handleStockLocked(StockLockedTo stockLockedTo, Message message){
        log.info("收到解锁库存的消息");
        Long id = stockLockedTo.getId();
        StockDetailTo detail = stockLockedTo.getDetail();
        // 解锁
        *//* 1、查询数据库关于这个订单的锁定库存信息。
             有 解锁
             解锁:订单情况。
                1、没有这个订单。必须解锁
                2、有这个订单。不是解锁库存。
                订单状态:已取消: 解锁库存
                没取消:不能解锁
             没有，库存锁定失败了，库存回滚了。这种情况无需解锁
        *//*
        WareOrderTaskDetailEntity byId = orderTaskDetailService.getById(detail.getId());
        if(byId != null){
            // 解锁
        }else{
            // 无需解锁
            log.info("没有找到库存信息，库存锁定失败，无需解锁");
        }

    }*/
    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        QueryWrapper<WareSkuEntity> queryWrapper = new QueryWrapper<>();

        String skuId = (String) params.get("skuId");
        if (!StringUtils.isEmpty(skuId) && !"0".equalsIgnoreCase(skuId)) {
            queryWrapper.eq("sku_id", skuId);
        }

        String wareId = (String) params.get("wareId");
        if (!StringUtils.isEmpty(wareId) && !"0".equalsIgnoreCase(wareId)) {
            queryWrapper.eq("ware_id", wareId);
        }

        IPage<WareSkuEntity> page = this.page(
                new Query<WareSkuEntity>().getPage(params),
                queryWrapper
        );
        return new PageUtils(page);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void addStock(Long skuId, Long wareId, Integer skuNum) {
        //1、判读如果没有这个库存记录新增
        List<WareSkuEntity> wareSkuEntities = wareSkuDao.selectList(
                new QueryWrapper<WareSkuEntity>().eq("sku_id", skuId).eq("ware_id", wareId));

        if (wareSkuEntities == null || wareSkuEntities.size() == 0) {
            WareSkuEntity wareSkuEntity = new WareSkuEntity();
            wareSkuEntity.setSkuId(skuId);
            wareSkuEntity.setStock(skuNum);
            wareSkuEntity.setWareId(wareId);
            wareSkuEntity.setStockLocked(0);
            //TODO 远程查询sku的名字，如果失败整个事务无需回滚
            //1、自己catch异常
            // TODO 如果用其他办法让异常部分不回滚
            try{
                R info = productFeignService.info(skuId);
                Map<String,Object> data = (Map<String, Object>) info.get("skuInfo");
                if (info.getCode() == 0) {
                    wareSkuEntity.setSkuName((String) data.get("skuName"));
                }
            } catch (Exception ignored) {

            }
            //添加库存信息
            wareSkuDao.insert(wareSkuEntity);
        } else {
            //修改库存信息
            wareSkuDao.addStock(skuId,wareId,skuNum);
        }

    }

    @Override
    public List<SkuHasStockVo> getSkusHasStock(List<Long> skuIds) {
        return skuIds.stream().map((skuId) -> {
            SkuHasStockVo vo = new SkuHasStockVo();
            // 查询当前sku的总库存量
            // SELECT SUM(stock-stock_locked) FROM `wms_ware_sku` WHERE sku_id = 1
            // 这里用long类型的会出现 null 的返回，而long类型接受不了null， 只能用其包装类 Long
            Long count = baseMapper.getSkusStock(skuId);
            vo.setSkuId(skuId);
            vo.setHasStock(count != null && count > 0);
            return vo;
        }).collect(Collectors.toList());
    }

    /**
     * 库存锁定，sql执行锁定锁定
     *
     * @param lockTo 待锁定订单项
     * @return 锁定结果
     * &#064;Transactional(rollbackFor  = NoStockException.class)：指定的异常出现会导致回滚
     * 未指定异常，任何运行时异常都会导致回滚，可以省略rollbackFor
     * 库存解锁的场景
     * 1)、下订单成功，订单过期没有支付被系统自动取消、或被用户手动取消。都要解锁库存
     * 2)、下订单成功，库存锁定成功，接下来的业务调用失败，导致订单回滚了，之前锁定的库存就要自动解锁。
     */
    @Transactional
    @Override
    public void orderLockStock(WareSkuLockTo lockTo) {
        // 按照收货地址找到就近仓库，锁定库存（暂未实现）
        // 采用方案：获取每项商品在哪些仓库有库存，轮询尝试锁定，任一商品锁定失败回滚
        // 1.往库存工作单存储当前锁定（本地事务表）
        WareOrderTaskEntity taskEntity = new WareOrderTaskEntity();
        taskEntity.setOrderSn(lockTo.getOrderSn());
        orderTaskService.save(taskEntity);

        // 2.封装待锁定库存项Map封装待锁定库存项Map
        Map<Long, OrderItemVo> lockItemMap = lockTo.getLocks().stream().collect(Collectors.toMap(OrderItemVo::getSkuId, val -> val));
        // 3.查询（库存 - 库存锁定 >= 待锁定库存数）的仓库
        List<WareSkuEntity> wareEntities = baseMapper.selectListHasSkuStock(lockItemMap.keySet())
                .stream()
                .filter(entity -> entity.getStock() - entity.getStockLocked() >= lockItemMap.get(entity.getSkuId()).getCount())
                .collect(Collectors.toList());
        // 判断是否查询到仓库
        if (CollectionUtils.isEmpty(wareEntities)) {
            // 匹配失败，所有商品项没有库存
            Set<Long> skuIds = lockItemMap.keySet();
            throw new NoStockException(skuIds);
        }
        // 将查询出的仓库数据封装成Map，key:skuId  val:wareId
        Map<Long, List<WareSkuEntity>> wareMap = wareEntities.stream().collect(Collectors.groupingBy(WareSkuEntity::getSkuId));
        // 4.判断是否为每一个商品项至少匹配了一个仓库
        List<WareOrderTaskDetailEntity> taskDetails = new ArrayList<>();// 库存锁定工作单详情
        Map<Long, StockLockedTo> lockedMessageMap = new HashMap<>();// 库存锁定工作单消息
        // 看有库存的仓库个数是否 >= 待锁定商品项个数
        if (wareMap.size() < lockTo.getLocks().size()) {
            // 匹配失败，部分商品没有库存
            Set<Long> skuIds = lockItemMap.keySet();
            skuIds.removeAll(wareMap.keySet());// 求商品项差集
            throw new NoStockException(skuIds);
        } else {
            // 所有商品都存在有库存的仓库
            // 5.锁定库存
            for (Map.Entry<Long, List<WareSkuEntity>> entry : wareMap.entrySet()) {
                boolean skuStocked = false;
                Long skuId = entry.getKey();// 商品
                OrderItemVo item = lockItemMap.get(skuId);
                Integer count = item.getCount();// 待锁定个数
                List<WareSkuEntity> hasStockWares = entry.getValue();// 有足够库存的仓库
                // 遍历仓库，看着在哪个仓库里面锁定
                for (WareSkuEntity ware : hasStockWares) {
                    Long num = baseMapper.lockSkuStock(skuId, ware.getWareId(), count);
                    if (num == 1) {
                        // 锁定成功，跳出循环
                        skuStocked = true;
                        // 创建库存锁定工作单详情（每一件商品锁定详情）
                        WareOrderTaskDetailEntity taskDetail = new WareOrderTaskDetailEntity(null, skuId,
                                item.getTitle(), count, taskEntity.getId(), ware.getWareId(),
                                WareOrderTaskConstant.LockStatusEnum.LOCKED.getCode());
                        taskDetails.add(taskDetail);
                        // 创建库存锁定工作单消息（每一件商品一条消息）
                        StockDetailTo detailMessage = new StockDetailTo();
                        BeanUtils.copyProperties(taskDetail, detailMessage);
                        StockLockedTo lockedMessage = new StockLockedTo(taskEntity.getId(), detailMessage);
                        lockedMessageMap.put(skuId, lockedMessage);
                        break;
                    }
                }
                if (!skuStocked) {
                    // 匹配失败，当前商品所有仓库都未锁定成功
                    throw new NoStockException(skuId);
                }
            }
        }

        // 6.往库存工作单详情存储当前锁定（本地事务表）
        orderTaskDetailService.saveBatch(taskDetails);

        // 7.发送消息
        for (WareOrderTaskDetailEntity taskDetail : taskDetails) {
            StockLockedTo message = lockedMessageMap.get(taskDetail.getSkuId());
            message.getDetail().setId(taskDetail.getId());// 存储库存详情ID
            rabbitTemplate.convertAndSend("stock-event-exchange", "stock.locked", message);
        }
    }
    /**
     * 库存解锁
     */
    @Override
    public void unLockStock(StockLockedTo locked) throws Exception {
        StockDetailTo taskDetailTo = locked.getDetail();// 库存工作单详情TO
        WareOrderTaskDetailEntity taskDetail = orderTaskDetailService.getById(taskDetailTo.getId());// 库存工作单详情Entity
        /* 1、查询数据库关于这个订单的锁定库存信息。
             有 解锁
             解锁: 根据订单情况。
                1、没有这个订单。必须解锁
                2、有这个订单。不是解锁库存。
                订单状态:已取消: 解锁库存
                没取消:不能解锁
             没有，库存锁定失败了，库存回滚了。这种情况无需解锁
        */
        if (taskDetail != null) {
            // 1.工作单未回滚，需要解锁
            WareOrderTaskEntity task = orderTaskService.getById(locked.getId());// 库存工作单Entity
            R r = orderFeignService.getOrderByOrderSn(task.getOrderSn());// 订单Entity
            if (r.getCode() == 0) {
                // 订单数据返回成功
                OrderTo order = r.getData(new TypeReference<OrderTo>() {
                });
                if (order == null || OrderConstant.OrderStatusEnum.CANCLED.getCode().equals(order.getStatus())) {
                    // 2.订单已回滚 || 订单未回滚已取消状态(订单关单才能解锁库存)
                    if (WareOrderTaskConstant.LockStatusEnum.LOCKED.getCode().equals(taskDetail.getLockStatus())) {
                        // 订单已锁定状态，需要解锁（消息确认）
                        unLockStock(taskDetailTo.getSkuId(), taskDetailTo.getWareId(), taskDetailTo.getSkuNum(), taskDetailTo.getId());
                    } else {
                        // 订单其他状态，不可解锁（消息确认）
                    }
                }
            } else {
                // 订单远程调用失败（消息重新入队）
                throw new Exception();
            }
        } else {
            // 3.无库存锁定工作单记录，已回滚，无需解锁（消息确认）
        }
    }

    /**
     * 库存确定可以解锁
     * 1.sql执行释放锁定(恢复库存数量）
     * 2.更新库存工作单状态为已解锁
     *
     * @param skuId
     * @param wareId
     * @param count
     */
    @Override
    public void unLockStock(Long skuId, Long wareId, Integer count, Long taskDetailId) {
        // 1.库存解锁
        baseMapper.unLockStock(skuId, wareId, count);

        // 2.更新工作单的状态 已解锁
        WareOrderTaskDetailEntity taskDetail = new WareOrderTaskDetailEntity();
        taskDetail.setId(taskDetailId);
        taskDetail.setLockStatus(WareOrderTaskConstant.LockStatusEnum.UNLOCKED.getCode());
        orderTaskDetailService.updateById(taskDetail);
    }
    /**
     * 库存解锁
     * 订单解锁触发，防止库存解锁消息优先于订单解锁消息到期，导致库存无法解锁
     */
    @Transactional
    @Override
    public void unLockStock(OrderTo order) {
        String orderSn = order.getOrderSn();// 订单号
        // 1.根据订单号查询库存锁定工作单
        WareOrderTaskEntity task = orderTaskService.getOrderTaskByOrderSn(orderSn);
        // 2.按照工作单查询未解锁的库存，进行解锁
        List<WareOrderTaskDetailEntity> taskDetails = orderTaskDetailService.list(new QueryWrapper<WareOrderTaskDetailEntity>()
                .eq("task_id", task.getId())
                .eq("lock_status", WareOrderTaskConstant.LockStatusEnum.LOCKED.getCode()));// todo 并发问题
        // 3.解锁库存
        for (WareOrderTaskDetailEntity taskDetail : taskDetails) {
            unLockStock(taskDetail.getSkuId(), taskDetail.getWareId(), taskDetail.getSkuNum(), taskDetail.getId());
        }
    }
}